package FrameWork.PathFinding;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class NodeMap {
	private Node[][] nodes;
	protected List<Node> liveingNodes;
	private Map map;
	private int startX,startY,endX=5,endY=5;
	private Heuristic heuristic = new AStar();
	private boolean useHeuristic = false;
	public NodeMap(Map map){
		setMap(map);
		nodes = new Node[map.getTileWidth()][map.getTileHeight()];
		liveingNodes = new ArrayList<Node>();
	}
	public void clear(){
		nodes = new Node[map.getTileWidth()][map.getTileHeight()];
		liveingNodes.clear();
	}
	public Heuristic getHeuristic(){
		return heuristic;
	}
	public void run(int startX,int startY,int max){
		if(startX != this.startX || startY != this.startY){
			clear();
		}
		this.startX=startX;
		this.startY=startY;
		List<Node> nextNodes = new ArrayList<Node>();
		
		Node firstNode = new Node(startX, startY, 1);
		addLiveingNode(firstNode);
		
		for(Node node:liveingNodes){
			heuristic.findH(node, this);
		}
		
		for(int i = 0;i<max && getValue(getEndX(), getEndY()) == 0;i++){
			
			if(useHeuristic)
				nextNodes = heuristic.getCutOffPoint(liveingNodes);
			else
				nextNodes = new ArrayList<Node>(liveingNodes);
					
			for(Node node:nextNodes){
				spread(node);
		}
		}
	}
	private void spread(Node node){
		check(1, 0, node);
		check(0, 1, node);
		
		check(-1, 0, node);
		check(0, -1, node);
		
		
		check(1, 1, node);
		check(-1, -1,node);
		
		check(1, -1, node);
		check(-1, 1, node);
		
		removeLiveingNode(node);
	}
	private void check(int x,int y,Node node){
		if(!hasObstacle(node.getX()+x, node.getY()+y)){
			if(!hasNode(node.getX()+x, node.getY()+y)){
				Node nextNode = new Node(node.getX()+x, node.getY()+y, node.getValue()+1);
				addLiveingNode(nextNode);
			}else if(getNode(node.getX()+x, node.getY()+y).getValue() > node.getValue()+1){
				getNode(node.getX()+x, node.getY()+y).setValue(node.getValue()+1);
				addLiveingNode(getNode(node.getX()+x, node.getY()+y));
			}
		}
	}
	public Path run(int startX,int startY,int endX,int endY,int max){
		this.endX=endX;
		this.endY=endY;
		
		run(startX, startY, max);
		Path path = getPath(endX, endY);
		if(!path.checkCurrentPath()){
			clear();
			run(startX, startY, max);
			path = getPath(endX, endY);
		}
		
		return path;
	}
	public Node getNode(int x,int y){
		if(x>0 && y>0 && x<map.getTileWidth() && y<map.getTileHeight()){
			return nodes[x][y];
		}else{
			return null;
		}
	}
	public Path getPath(int x,int y){
		return new Path(x,y);
	}
	public boolean hasNode(int x,int y){
		return getNode(x,y) != null;
	}
	public int getValue(int x,int y){
		Node node = getNode(x,y);
		if(node != null)
			return node.getValue();
		else
			return 0;
	}
	public boolean hasObstacle(int x,int y){
		return map.getSolid(x, y);
	}
	public int getWeight(int x,int y){
		return map.getWeight(x, y);
	}
	protected void addNode(Node node){
		nodes[node.getX()][node.getY()] = node;
	}
	public void setMap(Map map){
		this.map=map;
	}
	public Map getMap(){
		return map;
	}
	public int getStartX(){
		return startX;
	}
	public int getStartY(){
		return startY;
	}
	public int getEndX(){
		return endX;
	}
	public int getEndY(){
		return endY;
	}
	
	protected void addLiveingNode(Node node) {
		liveingNodes.add(node);
		node.setLiveing(true);
		if(hasNode(node.getX(), node.getY())){
			removeLiveingNode(getNode(node.getX(), node.getY()));
		}
		addNode(node);
		if(useHeuristic)
			heuristic.findH(node, this);
	}
	protected void removeLiveingNode(Node node) {
		node.setLiveing(false);
		liveingNodes.remove(node);
	}
	public class Path {
		private List<Step> steps = new ArrayList<Step>();
		private int inc = 0;
		public Path(int endX,int endY){
			findPath(endX, endY);
		}
		private void findPath(int endX,int endY){
			int x = endX,
				y = endY;
			boolean runing = true;
			while(runing){
				
				addStep(x, y);
				int value = getValue(x, y);
				
				
				
				if(value > getValue(x+1, y) && getValue(x+1, y)>0)
					x++;
				else if(value > getValue(x-1, y) && getValue(x-1, y)>0)
					x--;
				
				else if(value > getValue(x, y+1) && getValue(x, y+1)>0)
					y++;
				else if(value > getValue(x, y-1) && getValue(x, y-1)>0)
					y--;
				
				
				else if(value > getValue(x+1, y+1) && getValue(x+1, y+1)>0){
					x++;
					y++;
				}
				else if(value > getValue(x-1, y+1) && getValue(x-1, y+1)>0){
					x--;
					y++;
				}
				else if(value > getValue(x+1, y-1) && getValue(x+1, y-1)>0){
					x++;
					y--;
				}
				else if(value > getValue(x-1, y-1) && getValue(x-1, y-1)>0){
					x--;
					y--;
				}
				else{
					runing = false;
				}
			}
			
			Collections.reverse(steps);
			
		}
		public void addStep(int x,int y){
			steps.add(new Step(x, y,getValue(x, y)));
		}
		public Step getStep(int index){
			return steps.get(index);
		}
		public int getStepX(int index){
			return steps.get(index).getX();
		}
		public int getStepY(int index){
			return steps.get(index).getY();
		}
		public int getSize(){
			return steps.size();
		}
		public Step nextStep(){
			return steps.get(inc++);
		}
		public boolean checkCurrentPath(){
			for(Step s:steps)
			{
				if(map.getSolid(s.getX(), s.getY())){
					return false;
				}
			}
			return (steps.get(0).getX()==getStartX() && steps.get(0).getY()==getStartY());
		}
		public boolean isFinished(){
			return inc >= steps.size();
		}
	}
	
}
